package com.sunlands.zlcx.usercenter.service;

import com.sunlands.zlcx.usercenter.client.LiveVideoService;
import com.sunlands.zlcx.usercenter.client.SocketService;
import com.sunlands.zlcx.usercenter.client.bean.ShareMsgParamVO;
import com.sunlands.zlcx.usercenter.constant.AppEnum;
import com.sunlands.zlcx.usercenter.constant.YN;
import com.sunlands.zlcx.usercenter.constant.dic.STUDY_OPEN_DAYS;
import com.sunlands.zlcx.usercenter.domain.*;
import com.sunlands.zlcx.usercenter.exception.CheckParamException;
import com.sunlands.zlcx.usercenter.repository.*;
import com.sunlands.zlcx.usercenter.service.task.TaskVipChargingServiceImpl;
import com.sunlands.zlcx.usercenter.util.BeanPropertiesUtil;
import com.sunlands.zlcx.usercenter.util.DateUtil;
import com.sunlands.zlcx.usercenter.util.Version;
import com.sunlands.zlcx.usercenter.vo.UsersVO;
import com.sunlands.zlcx.usercenter.vo.VipPrivilegeVO;
import com.sunlands.zlcx.usercenter.vo.VipUserVO;
import com.sunlands.zlcx.usercenter.vo.VipVO;
import com.sunlands.zlcx.usercenter.vo.feign.RoomPublisherVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.math.BigInteger;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.sunlands.zlcx.usercenter.config.RedisConfig.CacheKeys.*;
import static com.sunlands.zlcx.usercenter.constant.MessageType.*;
import static com.sunlands.zlcx.usercenter.domain.UsersDO.StatusEnums.LIVE_LEARNING;
import static com.sunlands.zlcx.usercenter.domain.VipUserDO.LevelEnum.*;
import static com.sunlands.zlcx.usercenter.util.DateUtil.formatter;

/**
 * @author shixiaopeng
 */
@Service
@Slf4j
public class VipUserServiceImpl implements BeanPropertiesUtil {

    @Autowired
    private VipSetMealRepository vipSetMealRepository;
    @Autowired
    private VipUserRepository vipUserRepository;
    @Autowired
    private VipUserSetMealRecordRepository vipUserSetMealRecordRepository;
    @Autowired
    private VipUserUseRecordRepository vipUserUseRecordRepository;
    @Autowired
    private VipPrivilegeRepository vipPrivilegeRepository;
    @Autowired
    Version versionService;
    @Autowired
    private UsersServiceImpl usersService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource(name = "vipChargingExecutor")
    ThreadPoolTaskScheduler vipChargingExecutor;
    @Resource(name = "gyfTaskExecutor")
    AsyncTaskExecutor gyfTaskExecutor;
    @Autowired
    @Lazy
    private TaskVipChargingServiceImpl taskVipChargingService;
    @Autowired
    private LiveVideoService liveVideoService;
    @Autowired
    private SocketService socketService;
    @Autowired
    private DicServiceImpl dicService;
    @Autowired
    private VipUserServiceImpl vipUserService;

    public static final long DEFAULT_OPEN_DAY_TOTAL_MINUTES = 10000000L;

    @Cacheable(cacheNames = VIP_USER_PRIVILEGE, key = "'all'", unless = "#result == null")
    public List<VipPrivilegeVO> privilege() {

        List<VipPrivilegeDO> privilegeDOList = vipPrivilegeRepository.findAll();
        return VipPrivilegeVO.fromDO(privilegeDOList);
    }


    @Cacheable(cacheNames = VIP_USER_LIST_INFO, key = "#userId")
    public List<VipUserDO> allVipUsers(Long userId) {
        List<VipUserDO> vips = vipUserRepository.findAllByUserIdOrderByLevelDesc(userId);
        if (vips.stream().noneMatch(vip -> STUDY_OPEN_DAY.code.equals(vip.getLevel()))) {
            VipUserDO vipUserDO = VipUserDO.builder()
                    .level(STUDY_OPEN_DAY.code)
                    .userId(userId)
                    .totalTime(DEFAULT_OPEN_DAY_TOTAL_MINUTES)
                    .status(YN.YES.getCode())
                    .useredTime(0L)
                    .preUsedTime(0L)
                    .build();
            vipUserRepository.save(vipUserDO);
            vips.add(vipUserDO);
        }
        return vips.stream().sorted(Comparator.comparing(VipUserDO::getLevel).reversed()).collect(Collectors.toList());
    }

    public VipUserDO checkLive(Long userId) {
        List<VipUserDO> validVipList = validVips(userId);
        if (validVipList == null) {
            return null;
        }
        Map<Integer, List<VipUserDO>> listMap = validVipList.stream().collect(Collectors.groupingBy(VipUserDO::getLevel));

        List<Integer> levelList = validVipList.stream().map(VipUserDO::getLevel).collect(Collectors.toList());
        for (Integer level : levelList) {
            List<VipUserDO> vipUserDOS = listMap.get(level);
            if (vipUserDOS != null && vipUserDOS.size() > 0) {
                VipUserDO vipUserDO = vipUserDOS.get(0);
                if (vipUserDO.getUseredTime() < vipUserDO.getTotalTime()) {
                    return vipUserDO;
                }
            }
        }
        return null;
    }

    /**
     * v3.4.3之后废弃
     */
    public VipUserVO checkVip(Long userId) {
        List<VipUserDO> validVipList = validVips(userId);
        if (validVipList == null || validVipList.isEmpty()) {
            return null;
        }
        List<VipUserDO> userDOS = validVipList.stream().filter(vipUserDO -> vipUserDO.getLevel().intValue() != VipUserDO.LevelEnum.FREE_TIMES.code.intValue()).collect(Collectors.toList());
        if (userDOS.isEmpty()) {
            return null;
        }

        VipUserDO vipUserDO = userDOS.get(0);
        Date expireDate = userDOS.stream().filter(vipUser -> Objects.nonNull(vipUser.getExpireDate())).map(VipUserDO::getExpireDate).max(Date::compareTo).orElse(null);

        VipUserVO vipUserVO = VipUserVO.fromDO(vipUserDO);
        vipUserVO.setExpireDate(expireDate);
        return vipUserVO;
    }

    public List<VipUserDO> validVips(Long userId) {
        List<VipUserDO> vipUserList = allVipUsers(userId);
        return validVipFilter(vipUserList);
    }

    public List<VipUserDO> validVipFilter(List<VipUserDO> vipUserList){
        if(Objects.isNull(vipUserList)){
            return Collections.emptyList();
        }
        return vipUserList.stream()
                .filter(vipUserDO -> vipUserDO.getStatus().intValue() == VipUserDO.StatusEnum.valid.code)
                .filter(vipUserDO -> YN.YES.getCode().equals(of(vipUserDO.getLevel()).vip))
                .sorted(Comparator.comparing(VipUserDO::getLevel).reversed())
                .collect(Collectors.toList());
    }

//    public List<VipUserDO> getAllVipUserList(Long userId) {
//        List<VipUserDO> vipUserList = vipUserDOList(userId);
//        //没有记录
//        if (Objects.isNull(vipUserList) || vipUserList.isEmpty()) {
//            return null;
//        }
//        List<VipUserDO> validVipList = vipUserList.stream().sorted(Comparator.comparing(VipUserDO::getLevel).reversed()).collect(Collectors.toList());
//        //都过期了
//        if (validVipList.isEmpty()) {
//            return null;
//        }
//        return validVipList;
//    }

    @Cacheable(cacheNames = SET_MEAL_LIST, key = "#os")
    public List<VipSetMealDO> findByOs(String os) {
        return vipSetMealRepository.findAllByOsAndShowOrderBySortDesc(os, 1);
    }

    @Cacheable(cacheNames = SET_MEAL_BY_ID, key = "#id")
    public VipSetMealDO findSetMealById(Long id) {
        return vipSetMealRepository.findOne(id);
    }


    @Caching(evict = {
            @CacheEvict(cacheNames = VIP_USER_INFO, key = "#vipVO.userId", condition = "#vipVO.userId != null"),
            @CacheEvict(cacheNames = VIP_USER_LIST_INFO, key = "#vipVO.userId", condition = "#vipVO.userId != null")
    })
    @Transactional(rollbackFor = Exception.class)
    public VipUserDO openVip(VipVO vipVO) {
        VipSetMealDO setMealDO = findSetMealById(vipVO.getSetMealId());
        int levelFlag = setMealDO.getLevel();
        VipUserSetMealRecordDO vipUserSetMealRecordDO = VipUserSetMealRecordDO
                .builder()
                .setMealId(vipVO.getSetMealId())
                .userId(vipVO.getUserId())
                .operateType(vipVO.getOperateType())
                .orderNo(vipVO.getOrderNo())
                .type(setMealDO.getType())
                .build();

        vipUserSetMealRecordRepository.save(vipUserSetMealRecordDO);

        List<VipUserDO> vipUserDOS = allVipUsers(vipVO.getUserId());
        List<VipUserDO> vipUserDOList = vipUserDOS.stream().filter(vipUserDO -> levelFlag == vipUserDO.getLevel()).collect(Collectors.toList());
        //以下三种情况，1从未开通过vip，2vip用户还在有效期，延长使用，3vip已过期，有记录
        ZoneId zone = ZoneId.systemDefault();
        VipUserDO vipUserDO;
        if (vipUserDOList.size() > 0) {
            vipUserDO = lockById(vipUserDOList.get(0).getId());
            if (levelFlag == HOUR_TIMES.code) {
                if (vipUserDO.getTotalTime().intValue() > 0 && vipUserDO.getTotalTime().intValue() == vipUserDO.getUseredTime().intValue()) {
                    vipUserDO.setTotalTime(setMealDO.getMonthTime());
                    vipUserDO.setUseredTime(0L);
                    vipUserDO.setStatus(VipUserDO.StatusEnum.valid.code);
                } else {
                    vipUserDO.setTotalTime(vipUserDO.getTotalTime() + setMealDO.getMonthTime());
                }
            } else if (levelFlag == VIP_7DAY.code || levelFlag == VIP_COMMON.code) {
                //vip用户还在有效期，延长使用
                Date expireDate = vipUserDO.getExpireDate();
                if (expireDate.compareTo(new Date()) == 1) {
                    if (levelFlag == VIP_7DAY.code) {
                        LocalDateTime expireLocalDateTime = expireDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                        LocalDateTime localDateTime = expireLocalDateTime.plusDays(setMealDO.getMonths());
                        vipUserDO.setExpireDate(Date.from(localDateTime.atZone(zone).toInstant()));
                        //修改临时卡的总时长
                        vipUserDO.setTotalTime(setMealDO.getMonthTime());
                    } else {
                        LocalDateTime expireLocalDateTime = expireDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                        LocalDateTime localDate = expireLocalDateTime.plusMonths(setMealDO.getMonths());
                        Instant instant = localDate.atZone(zone).toInstant();
                        java.util.Date date = Date.from(instant);
                        vipUserDO.setExpireDate(date);

                    }
                } else {
                    //vip已过期,重置信息,先判断定时任务是否执行了该记录
                    if (vipUserDO.getStatus().intValue() == VipUserDO.StatusEnum.valid.code.intValue()) {
                        //保存记录
                        VipUserUseRecordDO vipUserUseRecordDO = new VipUserUseRecordDO();
                        LocalDate today = LocalDate.now();
                        Instant todayInstant = today.atStartOfDay().atZone(zone).toInstant();
                        java.util.Date todayDate = Date.from(todayInstant);
                        vipUserUseRecordDO.setExpireDate(todayDate);
                        vipUserUseRecordDO.setTotalTime(vipUserDO.getTotalTime());
                        vipUserUseRecordDO.setUseredTime(vipUserDO.getUseredTime());
                        vipUserUseRecordDO.setUserId(vipUserDO.getUserId());
                    }
                    vipUserDO.setStatus(VipUserDO.StatusEnum.valid.code);

                    if (levelFlag == VIP_7DAY.code) {
                        openVip7day(setMealDO, zone, vipUserDO);
                    } else {
                        openVipCommon(setMealDO, zone, vipUserDO);
                    }

                    vipUserDO.setUseredTime(0L);
                }
            }
        } else {
            //从未开通过这类vip
            vipUserDO = new VipUserDO();
            vipUserDO.setLevel(levelFlag);
            vipUserDO.setUserId(vipVO.getUserId());
            vipUserDO.setTotalTime(setMealDO.getMonthTime());
            vipUserDO.setUseredTime(0L);

            if (levelFlag == VIP_7DAY.code) {
                openVip7day(setMealDO, zone, vipUserDO);
            } else if (levelFlag == VIP_COMMON.code) {
                openVipCommon(setMealDO, zone, vipUserDO);

            }
            vipUserDO.setStatus(VipUserDO.StatusEnum.valid.code);
        }

        //开通vip时，将用户vip状态保存到用户表
        UsersDO usersDO = usersService.getUsersRepository().findOne(vipVO.getUserId());
        if (Objects.isNull(usersDO.getVipStatus()) || YN.YES.getCode() != usersDO.getVipStatus()) {
            usersDO.setVipStatus(YN.YES.getCode());
            usersService.save(usersDO);
        }
        return vipUserRepository.save(vipUserDO);
    }

    private void openVipCommon(VipSetMealDO setMealDO, ZoneId zone, VipUserDO vipUserDO) {
        Date now = new Date();
        //设置vip到期时间
        LocalDateTime localDateTime = now.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        LocalDateTime localDate = localDateTime.plusMonths(setMealDO.getMonths());
        Instant instant = localDate.atZone(zone).toInstant();
        java.util.Date date = Date.from(instant);
        vipUserDO.setExpireDate(date);
        //设置当月的过期时间
        LocalDateTime currLocalDate = localDateTime.plusMonths(1);
        Instant currInstant = currLocalDate.atZone(zone).toInstant();
        Date currDate = Date.from(currInstant);
        vipUserDO.setCurrExpireDate(currDate);
        vipUserDO.setTakeEffectDate(now);
    }

    private void openVip7day(VipSetMealDO setMealDO, ZoneId zone, VipUserDO vipUserDO) {
        Date now = new Date();
        vipUserDO.setTakeEffectDate(now);
        //设置vip到期时间
        LocalDateTime expireLocalDateTime = now.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        LocalDateTime localDateTime = expireLocalDateTime.plusDays(setMealDO.getMonths());

        vipUserDO.setExpireDate(Date.from(localDateTime.atZone(zone).toInstant()));
        vipUserDO.setCurrExpireDate(Date.from(localDateTime.atZone(zone).toInstant()));
        //总时长
        vipUserDO.setTotalTime(setMealDO.getMonthTime());
    }

    /**
     * vip退单处理
     *
     * @param vipVO vipVO
     * @return
     */
    @Caching(evict = {
            @CacheEvict(cacheNames = VIP_USER_INFO, key = "#vipVO.userId", condition = "#vipVO.userId != null"),
            @CacheEvict(cacheNames = VIP_USER_LIST_INFO, key = "#vipVO.userId", condition = "#vipVO.userId != null")
    })
    @Transactional(rollbackFor = Exception.class)
    public VipUserDO returnVip(VipVO vipVO) {
        VipSetMealDO setMealDO = findSetMealById(vipVO.getSetMealId());
        VipUserSetMealRecordDO vipUserSetMealRecordDO = VipUserSetMealRecordDO.builder().
                setMealId(vipVO.getSetMealId()).
                userId(vipVO.getUserId()).
                operateType(vipVO.getOperateType()).
                orderNo(vipVO.getOrderNo())
                .build();

        vipUserSetMealRecordRepository.save(vipUserSetMealRecordDO);

        List<VipUserDO> vipUserList = allVipUsers(vipVO.getUserId());
        //判断是否是vip用户
        if (vipUserList == null || vipUserList.isEmpty()) {
            log.info("用户{}不是vip用户，不需要关闭vip", vipVO.getUserId());
            return null;
        }
        List<VipUserDO> userDOList = vipUserList.stream().filter(vipUserDO -> setMealDO.getLevel().intValue() == vipUserDO.getLevel().intValue() && vipUserDO.getStatus().intValue() == VipUserDO.StatusEnum.valid.code.intValue() && vipUserDO.getUseredTime() < vipUserDO.getTotalTime()).collect(Collectors.toList());
        //时间用完了或过期了，也不减
        if (userDOList.isEmpty()) {
            log.info("vip用户" + vipVO.getUserId() + "已过期或时长已用完");
            return null;
        }
        VipUserDO vipUserDO = lockById(userDOList.get(0).getId());
        if (vipUserDO.getLevel().intValue() == VipUserDO.LevelEnum.VIP_COMMON.code.intValue()) {
            //退费，先减去对应的天数
            Date expireDate = vipUserDO.getExpireDate();
            LocalDate expireLocalDate = expireDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            LocalDate localDate = expireLocalDate.minusMonths(setMealDO.getMonths());
            ZoneId zone = ZoneId.systemDefault();
            Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
            Date date = Date.from(instant);
            vipUserDO.setExpireDate(date);
            //看是否还在有效期内
            if (date.compareTo(new Date()) < 0) {
                vipUserDO.setStatus(VipUserDO.StatusEnum.invalid.code);
            }

        } else {
            Date expireDate = vipUserDO.getExpireDate();
            LocalDateTime expireLocalDate = expireDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            LocalDateTime localDate = expireLocalDate.minusDays(setMealDO.getMonths());
            ZoneId zone = ZoneId.systemDefault();
            Instant instant = localDate.atZone(zone).toInstant();
            Date date = Date.from(instant);
            vipUserDO.setExpireDate(date);
            //看是否还在有效期内
            if (date.compareTo(new Date()) < 0) {
                vipUserDO.setStatus(VipUserDO.StatusEnum.invalid.code);
            }

        }
        return vipUserRepository.save(vipUserDO);
    }


    /**
     * vip 结束
     *
     * @param id
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public VipUserDO endVip(Long id) {
        //对数据加锁
        VipUserDO vipUserDO = lockById(id);
        if (Objects.nonNull(vipUserDO.getExpireDate()) && vipUserDO.getExpireDate().compareTo(new Date()) < 0) {
            saveRecord(vipUserDO);
            log.info("vip已经过期，关闭vip功能 vipUserDO = {}", vipUserDO);
            vipUserDO.setStatus(VipUserDO.StatusEnum.invalid.code);
        }

        if (HOUR_TIMES.code.equals(vipUserDO.getLevel()) && vipUserDO.getTotalTime().compareTo(vipUserDO.getUseredTime()) <= 0) {
            saveRecord(vipUserDO);
            log.info("vip已经过期，关闭vip功能 vipUserDO = {}", vipUserDO);
            vipUserDO.setStatus(VipUserDO.StatusEnum.invalid.code);
        }

        //关闭vip时，修改用户信息中vip状态
        VipUserDO vip = vipUserRepository.save(vipUserDO);
        List<VipUserDO> vipUserList = validVips(vipUserDO.getUserId());
        if (Objects.isNull(vipUserList) || vipUserList.isEmpty()) {
            UsersDO user = usersService.getUsersRepository().findOne(vip.getUserId());
            user.setVipStatus(YN.NO.getCode());
            usersService.save(user);
        }

        return vip;
    }

    /**
     * vip 刷新方法
     *
     * @param id id
     * @return VipUserDO
     */
    @Transactional(rollbackFor = Exception.class)
    public VipUserDO refreshVip(Long id) {
        Date now = new Date();
        //对数据加锁
        VipUserDO vipUserDO = lockById(id);
        if (vipUserDO.getExpireDate().compareTo(now) > 0
                && vipUserDO.getCurrExpireDate().compareTo(now) < 0) {
            saveRecord(vipUserDO);
            log.info("vip已经时间刷新，vipUserDO = {}", vipUserDO);
            Date nextCurrExpireDate = getNextExpireDate(vipUserDO);
            vipUserDO.setCurrExpireDate(nextCurrExpireDate);
            vipUserDO.setUseredTime(0L);

            //TODO应将6000L维护到会员等级中，暂不处理
            if (vipUserDO.getLevel().equals(VipUserDO.LevelEnum.VIP_COMMON.code)) {
                //6000L最好维护到商品表中
                vipUserDO.setTotalTime(6000L);
            }
        }
        return vipUserRepository.save(vipUserDO);
    }


    private Date getNextExpireDate(VipUserDO vipUserDO) {
        Calendar calendar = Calendar.getInstance();
        if (vipUserDO.getLevel().equals(VipUserDO.LevelEnum.VIP_COMMON.code)) {
            //生效日期
            calendar.setTime(vipUserDO.getCurrExpireDate());
            calendar.set(Calendar.MONTH,
                    calendar.get(Calendar.MONTH) + 1);
        } else if (vipUserDO.getLevel().equals(VIP_7DAY.code)) {
            calendar.setTime(vipUserDO.getCurrExpireDate());
            calendar.set(Calendar.HOUR,
                    calendar.get(Calendar.HOUR) + 24 * 7);
        } else {
            throw new CheckParamException("未知vip级别");
        }
        return calendar.getTime();
    }


    private void saveRecord(VipUserDO vipUserDO) {
        VipUserUseRecordDO vipUserUseRecordDO = new VipUserUseRecordDO();
        vipUserUseRecordDO.setExpireDate(vipUserDO.getExpireDate());
        vipUserUseRecordDO.setTotalTime(vipUserDO.getTotalTime());
        vipUserUseRecordDO.setUseredTime(vipUserDO.getUseredTime());
        vipUserUseRecordDO.setUserId(vipUserDO.getUserId());
        vipUserUseRecordDO.setLevel(vipUserDO.getLevel());

        vipUserUseRecordRepository.save(vipUserUseRecordDO);

        if (VipUserDO.LevelEnum.VIP_COMMON.code == vipUserDO.getLevel().intValue()) {
            //异步处理
            gyfTaskExecutor.execute(() -> {
                List<Integer> types = new ArrayList<>();
                types.add(VipSetMealDO.TypeEnum.month_card.code);
                types.add(VipSetMealDO.TypeEnum.season_card.code);
                types.add(VipSetMealDO.TypeEnum.year_card.code);
                List<VipSetMealDO> vipSetMealDOS = vipSetMealRepository.findAllByTypeIn(types);
                List<Long> setMealIds = vipSetMealDOS.stream().map(vipSetMealDO -> vipSetMealDO.getId()).collect(Collectors.toList());

                Map<Long, Long> setMealIdToMonths = new HashMap<>();
                vipSetMealDOS.forEach(vipSetMealDO -> {
                    setMealIdToMonths.put(vipSetMealDO.getId(), vipSetMealDO.getMonths());
                });
                handleAlone(setMealIds, setMealIdToMonths, BigInteger.valueOf(vipUserDO.getUserId()));
            });
        }
    }



    public List<VipSetMealDO> vipSetMealList(Long userId, String os) {
        List<VipSetMealDO> setMealDOS = findByOs(os);
        List<VipUserDO> vipUserDOS = allVipUsers(userId);
        vipUserDOS = vipUserService.validVipFilter(vipUserDOS);
        List<VipUserDO> vipUserDOList = vipUserDOS.stream()
                .filter(vipUserDO -> of(vipUserDO.getLevel()).vip.equals(YN.YES.getCode()))
                .filter(vipUserDO ->
                        vipUserDO.getStatus().intValue() == VipUserDO.StatusEnum.valid.code.intValue())
                .collect(Collectors.toList());
        //判断是否是vip用户
        Map<Integer, Date> dateMap = vipUserDOList.stream().filter(vip -> Objects.nonNull(vip.getExpireDate())).collect(Collectors.toMap(VipUserDO::getLevel, VipUserDO::getExpireDate));
        List<VipSetMealDO> mealDOList = setMealDOS.stream().filter(vipSetMealDO -> {
            Integer limitTimes = vipSetMealDO.getLimitTimes();
            if (limitTimes > 0) {
                Integer count = vipUserSetMealRecordRepository.countByUserIdAndTypeAndOperateType(userId, vipSetMealDO.getType(), VipUserSetMealRecordDO.OperateEnums.BUY.code);
                return (count < limitTimes);
            }
            return true;
        }).map((VipSetMealDO vipSetMealDO) -> {
            if (vipSetMealDO.getLevel().intValue() == VipUserDO.LevelEnum.VIP_COMMON.code.intValue()) {
                Date expireDate = dateMap.get(VipUserDO.LevelEnum.VIP_COMMON.code);
                if (Objects.nonNull(expireDate)) {
                    LocalDate localDate = expireDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                    LocalDate month = localDate.plusMonths(vipSetMealDO.getMonths());
                    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                    String dateStr = month.format(fmt);
                    vipSetMealDO.setContent("续费至" + dateStr);
                }
            }

            return vipSetMealDO;
        }).collect(Collectors.toList());

        checkAndroid(userId, os, mealDOList);
        return mealDOList;

    }

    private void checkAndroid(Long userId, String os, List<VipSetMealDO> setMealDOS) {
        if ("Android".equals(os)) {
            VipSetMealDO vipSetMealDO = setMealDOS.get(1);
            if (vipSetMealDO.getType().intValue() != VipSetMealDO.TypeEnum.season_card.code.intValue()) {

                for (int i = 0; i < setMealDOS.size(); i++) {
                    if (setMealDOS.get(i).getType() == VipSetMealDO.TypeEnum.season_card.code.intValue()) {
                        VipSetMealDO temp = setMealDOS.get(i);
                        temp.setSelected(1);
                        if (versionService.judgeVersion(userId, "") == 0) {
                            setMealDOS.set(i, vipSetMealDO);
                            setMealDOS.set(1, temp);
                        }
                        break;
                    }
                }
            }
        } else {
            List<VipSetMealDO> mealDOS = setMealDOS.stream().filter(vipSetMealDO -> vipSetMealDO.getSelected() == 1).collect(Collectors.toList());
            if (mealDOS.isEmpty()) {
                setMealDOS.forEach(vipSetMealDO -> {
                    if (vipSetMealDO.getType() == VipSetMealDO.TypeEnum.season_card.code.intValue()) {
                        vipSetMealDO.setSelected(1);
                    }
                });
            }
        }
    }

    /**
     * 连麦结束扣减时长
     */
    @Caching(evict = {
            @CacheEvict(cacheNames = VIP_USER_INFO, key = "#userId", condition = "#userId != null"),
            @CacheEvict(cacheNames = VIP_USER_LIST_INFO, key = "#userId", condition = "#userId != null")
    })
    @Transactional(rollbackFor = Exception.class)
    public void reduceTime(Long userId, Long studyMinutes) {
        preReduceTime(userId, studyMinutes, false);
        List<VipUserDO> vipUserList = allVipUsers(userId);
        log.info("连麦结束，扣减时长 userId = {} minute = {} vipUserList = {}", userId, studyMinutes, vipUserList);
        if (Objects.isNull(vipUserList) || vipUserList.isEmpty()) {
            return;
        }
        for (VipUserDO vipUserDO : vipUserList) {
            if (Objects.nonNull(vipUserDO.getPreUsedTime()) && vipUserDO.getPreUsedTime() > 0) {
                VipUserDO vip = lockById(vipUserDO.getId());
                //记录vip用量
                recordVipUsage(VipUserVO.fromDO(vipUserDO));
                vip.setPreUsedTime(0L);
                vipUserRepository.save(vip);
            }
        }

    }

    private void recordVipUsage(VipUserVO vip) {
        vipChargingExecutor.execute(() -> {
            UsersLiveLearnTimingRecordDO record = UsersLiveLearnTimingRecordDO
                    .builder()
                    .userId(vip.getUserId())
                    .studyMinutes(vip.getPreUsedTime())
                    .vipLeave(vip.getLevel())
                    .type(UsersLearnTimingDO.TypeEnums.LIAN_MAI.code)
                    .build();
            usersLiveLearnTimingRecordRepository.save(record);
        });
    }

    @Autowired
    private UsersLiveLearnTimingRecordRepository usersLiveLearnTimingRecordRepository;

    /**
     * 实时扣减时长
     *
     * @param userId userId
     */
    @Transactional(rollbackFor = Exception.class)
    public void preReduceTime(Long userId) {
        UsersVO user = usersService.findOne(userId);
        if (LIVE_LEARNING.code.equals(user.getStatus())) {
            log.debug("System.currentTimeMillis() = {} user.getStartLearnTime().getTime() = {}", System.currentTimeMillis(), user.getStartLearnTime().getTime());
            long minute = (System.currentTimeMillis() - user.getStartLearnTime().getTime()) / 1000 / 60;
            if (minute >= 0) {
                preReduceTime(user.getId(), minute, true);
            }
        }
    }


    public boolean hasMinute(List<VipUserDO> vipUserList) {
        for (VipUserDO vip : vipUserList) {
            if (vip.getTotalTime() - (Objects.nonNull(vip.getUseredTime()) ? vip.getUseredTime() : 0L) > 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * VIP实时扣时长
     *
     * @param userId 用户id
     * @param minute 扣费分钟数
     * @param send   是否发送vip到期用尽提醒
     */
    @Transactional(rollbackFor = Exception.class)
    public void preReduceTime(Long userId, Long minute, boolean send) {
        log.info("vip扣费 userId = {} minute = {}, send = {}", userId, minute, send);
        UsersVO user = usersService.findOne(userId);
        List<VipUserDO> allVipRecord = allVipUsers(userId);
        List<VipUserDO> vipUserList = allVipRecord.stream()
                .filter(vipUserDO -> vipUserDO.getStatus().intValue() == VipUserDO.StatusEnum.valid.code)
                .filter(vipUserDO -> {
                    if (AppEnum.postgraduate.name().equals(user.getApp()) || !isOpenDay()) {
                        return !STUDY_OPEN_DAY.code.equals(vipUserDO.getLevel());
                    }
                    return true;
                })
                .sorted(Comparator.comparing(VipUserDO::getLevel).reversed())
                .collect(Collectors.toList());

        //非会员处理 && 无免费时长处理
        if (vipUserList.isEmpty() || !hasMinute(vipUserList)) {
            //房间内（非体验），所有时长用尽
            if (minuteUseUp(userId, user.getLianMaiId()) && send) {
                sendSocket(userId, EXPERIENCE_TIMES.code);
            }
            setChargingLeaveValue(userId, user.getLianMaiId(), EXPERIENCE_TIMES.code);
            log.debug("userId = {} 不是会员，不进行预扣费处理", userId);
            return;
        }

        //预扣时长记录总和
        Long preUsedTimeTotal = totalPreMinute(allVipRecord);

        //本次需要预扣时长
        minute -= preUsedTimeTotal;
        for (VipUserDO vip : vipUserList) {
            //扣费前锁定数据
            VipUserDO vipUser = lockById(vip.getId());
            //剩余时长
            long surplus = vipUser.getTotalTime() - vipUser.getUseredTime();
            if (surplus <= 0 || vipUser.getStatus().equals(VipUserDO.StatusEnum.invalid.code)) {
                continue;
            }

            //对于10分钟内快要结束的时长套餐，加快扣费频率到1分钟一次
            if (needSpeedUp(vipUser)) {
                speedUpReduce(vipUser.getUserId(), 61);
            }

            //本次可扣时长
            long currentReduceMinute = Math.min(minute, surplus);
            if (minute > 0) {
                vipUser.setUseredTime((Objects.nonNull(vip.getUseredTime()) ? vip.getUseredTime() : 0L) + currentReduceMinute);
                vipUser.setPreUsedTime((Objects.nonNull(vip.getPreUsedTime()) ? vip.getPreUsedTime() : 0L) + currentReduceMinute);
                vipUserRepository.save(vipUser);
            }

            if (send) {
                try {
                    sendSocket(userId, vipUser.getLevel());
                } catch (Exception e) {//容错处理
                    log.error("", e);
                }
            }

            setChargingLeaveValue(userId, user.getLianMaiId(), vipUser.getLevel());
            log.info("实时扣时长 user = {} 总共需要扣除时长 minute = {} 本次被扣除时长 currentReduceMinute = {} leave = {}", userId, minute, currentReduceMinute, vipUser.getLevel());

            //如果此次套餐被耗尽时长，马上触发下一次扣费，这样可以及时触发通知
            if (vipUser.getTotalTime().equals(vip.getUseredTime())) {
                vipChargingExecutor.execute(() -> taskVipChargingService.preReduceTime(vipUser.getUserId()));
            }
            break;
        }

    }

    private boolean minuteUseUp(Long userId, Long lianmaiId) {
        Integer chargingLeave = getChargingLeaveValue(userId, lianmaiId);
        return Objects.nonNull(chargingLeave) && !chargingLeave.equals(EXPERIENCE_TIMES.code);
    }

    private Long totalPreMinute(List<VipUserDO> vipUserList) {
        return vipUserList.stream()
                .filter(vip -> Objects.nonNull(vip.getPreUsedTime()))
                .map(VipUserDO::getPreUsedTime).reduce(Long::sum).orElse(0L);
    }

    /**
     * 加速扣费
     *
     * @param userId vip
     */
    public void speedUpReduce(Long userId, Integer second) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.SECOND,
                calendar.get(Calendar.SECOND) + second);
        log.info("vip即将结束，增加扣费频次 userId = {}", userId);
        vipChargingExecutor.schedule(() -> taskVipChargingService.preReduceTime(userId), calendar.getTime());
    }

    private boolean needSpeedUp(VipUserDO vip) {
        long now = System.currentTimeMillis();
        long expire = ((Objects.nonNull(vip.getExpireDate()) ? vip.getExpireDate().getTime() : Long.MAX_VALUE) - now) / 1000 / 60;
        long currExpire = ((Objects.nonNull(vip.getCurrExpireDate()) ? vip.getCurrExpireDate().getTime() : Long.MAX_VALUE) - now) / 1000 / 60;
        long usedSurplusTime = vip.getTotalTime() - vip.getUseredTime();

        long vipInvalidMinute;
        vipInvalidMinute = Math.min(expire, currExpire);
        vipInvalidMinute = Math.min(vipInvalidMinute, usedSurplusTime);
        return vipInvalidMinute < 15;
    }

    private void sendSocket(Long userId, Integer vipLeave) {
        UsersVO user = usersService.findOne(userId);
        Integer preLeave = getChargingLeaveValue(userId, user.getLianMaiId());
        //如果是体验发送消息
        if (EXPERIENCE_TIMES.code.equals(vipLeave)) {
            if (VIP_COMMON.code.equals(preLeave)) {
                log.info("扣时长切换-VIP已经到期或时长耗尽，现在自动下麦 userId = {}", userId);
                sendSocket(userId, VIP_TIME_USE_UP.getType(), "VIP已经到期或时长耗尽，现在自动下麦");
            } else if (STUDY_OPEN_DAY.code.equals(preLeave)) {
                log.info("扣时长切换-学习开放日已经结束，快去购买会员吧~ userId = {}", userId);
                sendSocket(userId, STUDY_DAY_AND_VIP_TIME_USE_UP.getType(), "学习开放日已经结束，快去购买会员吧~");
            } else {
                log.info("扣时长切换-你的全部可用时长已经耗尽，现在自动下麦 userId = {}", userId);
                sendSocket(userId, VIP_TIME_USE_UP.getType(), "你的全部可用时长已经耗尽，现在自动下麦");
            }
            return;
        }

        if (Objects.isNull(user.getLianMaiId())) {
            log.warn("没有查询到用户连麦状态 user = {}", user);
            return;
        }

        if (Objects.isNull(preLeave) || preLeave.equals(vipLeave) || Objects.isNull(vipLeave)) {
            log.debug("扣费方式没有发生变化，不发送消息 userId = {} vipLeave = {}", userId, vipLeave);
            return;
        }

        VipUserDO.LevelEnum preLevelEnum = VipUserDO.LevelEnum.of(preLeave);
        VipUserDO.LevelEnum levelEnum = VipUserDO.LevelEnum.of(vipLeave);
        assert levelEnum != null;
        assert preLevelEnum != null;
        log.debug("preLevelEnum = {}, levelEnum = {} vipLeave = {} userId = {}", preLevelEnum, levelEnum, vipLeave, userId);

        if (preLevelEnum.code < levelEnum.code || EXPERIENCE_TIMES.code.equals(preLevelEnum.code)) {
            log.info("扣时长切换-切换到{}时长计时。userId = {}", levelEnum.desc, userId);
            sendSocket(userId, VIP_TIME_SWITCH.getType(), "切换到" + levelEnum.desc + "时长计时");

        } else if (preLevelEnum.code.equals(VIP_7DAY.code)) {
            log.info("扣时长切换-{}已经到期，开始使用{}时长计时。userId = {}", preLevelEnum.desc, levelEnum.desc, userId);
            sendSocket(userId, VIP_TIME_SWITCH.getType(), preLevelEnum.desc + "已经到期，开始使用" + levelEnum.desc + "时长计时。");

        } else if (preLevelEnum.code.equals(FREE_TIMES.code)) {
            log.info("扣时长切换-{}时长已经耗尽，开始使用{}时长计时。userId = {}", preLevelEnum.desc, levelEnum.desc, userId);
            sendSocket(userId, VIP_TIME_SWITCH.getType(), preLevelEnum.desc + "时长已经耗尽，开始使用" + levelEnum.desc + "时长计时");

        } else if (preLevelEnum.code.equals(VIP_COMMON.code)) {
            log.info("扣时长切换-{}已经耗尽或者过期，开始使用{}时长计时。userId = {}", preLevelEnum.desc, levelEnum.desc, userId);
            sendSocket(userId, VIP_TIME_SWITCH.getType(), preLevelEnum.desc + "已经耗尽或者过期，开始使用" + levelEnum.desc + "时长计时。");

        } else if (preLevelEnum.code.equals(HOUR_TIMES.code)) {
            log.info("扣时长切换-{}已经耗尽或者过期，开始使用{}时长计时。userId = {}", preLevelEnum.desc, levelEnum.desc, userId);
            sendSocket(userId, VIP_TIME_SWITCH.getType(), preLevelEnum.desc + "已经耗尽，开始使用" + levelEnum.desc + "时长计时。");

        } else {
            log.info("扣时长切换-{}已经失效，开始使用{}时长计时。userId = {}", preLevelEnum.desc, levelEnum.desc, userId);
            sendSocket(userId, VIP_TIME_SWITCH.getType(), preLevelEnum.desc + "已经结束，开始使用" + levelEnum.desc + "时长计时。");
        }
    }

    private void sendSocket(Long userId, String type, String msg) {
        RoomPublisherVO publisherInfo = liveVideoService.getPublisherInfoByUserId(userId).getData();
        if (Objects.isNull(publisherInfo)) {
            log.warn("没有查询到用户连麦信息，停止发送时长切换消息 userId = {} publisherInfo = {}", userId, publisherInfo);
            return;
        }

        ShareMsgParamVO message = ShareMsgParamVO.builder()
                .type(type)
                .toId(publisherInfo.getRoomId().toString())
                .realToId(userId)
                .message(msg)
                .build();
        log.info("发送socket消息： message = {}", message);
        socketService.sendMessage(message);
    }

    @Transactional(rollbackFor = Exception.class)
    public VipUserDO lockById(Long id) {
        return vipUserRepository.findById(id);
    }


    public VipUserSetMealRecordDO findByOrderNoAndOperateType(String orderNo, Integer operateType) {
        return vipUserSetMealRecordRepository.findByOrderNoAndOperateType(orderNo, operateType);
    }


    /**
     * 查询所有的过期vip
     *
     * @param pageable pageable
     * @return Page<VipUserDO>
     */
    public Page<VipUserDO> findAllExpireVip(Pageable pageable) {
        Specification<VipUserDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("status"), VipUserDO.StatusEnum.valid.code));
            list.add(cb.lessThan(root.get("expireDate"), new Date()));
            return cb.and(list.toArray(new Predicate[list.size()]));
        };
        return vipUserRepository.findAll(specification, pageable);
    }

    /**
     * 查询所有的过期vip
     *
     * @param pageable pageable
     * @return Page<VipUserDO>
     */
    public Page<VipUserDO> findAllExpireHourTimesVip(Pageable pageable) {
        Specification<VipUserDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("status"), VipUserDO.StatusEnum.valid.code));
            list.add(cb.equal(root.get("level"), HOUR_TIMES.code));
            list.add(cb.lessThanOrEqualTo(root.get("totalTime"), root.get("useredTime")));
            return cb.and(list.toArray(new Predicate[list.size()]));
        };
        return vipUserRepository.findAll(specification, pageable);
    }

    public Page<VipUserDO> page(Pageable pageable) {
        Specification<VipUserDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("status"), VipUserDO.StatusEnum.valid.code));
            return cb.and(list.toArray(new Predicate[list.size()]));
        };
        return vipUserRepository.findAll(specification, pageable);
    }


    /**
     * 查询所有的过期vip
     *
     * @param pageable pageable
     * @return Page<VipUserDO>
     */
    public Page<VipUserDO> findAllRefreshVip(Pageable pageable) {
        Date now = new Date();
        Specification<VipUserDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("status"), VipUserDO.StatusEnum.valid.code));
            list.add(cb.greaterThan(root.get("expireDate"), now));
            list.add(cb.lessThan(root.get("currExpireDate"), now));
            return cb.and(list.toArray(new Predicate[0]));
        };
        return vipUserRepository.findAll(specification, pageable);
    }

    /**
     * 邀请成功后，赠送免费时长
     *
     * @param inviteCodeDO
     */
    @Transactional(rollbackFor = Exception.class)
    public void giveMinutes(InviteCodeDO inviteCodeDO) {
        log.info("赠送时长{}", inviteCodeDO);
        VipUserDO vipUserDO = vipUserRepository.findFirstByUserIdAndLevel(inviteCodeDO.getUserId(), VipUserDO.LevelEnum.FREE_TIMES.code);
        VipUserDO inviteVipUserDO = vipUserRepository.findFirstByUserIdAndLevel(inviteCodeDO.getInviteUserId(), VipUserDO.LevelEnum.FREE_TIMES.code);
        if (Objects.nonNull(vipUserDO)) {
            //邀请人有记录
            vipUserDO = lockById(vipUserDO.getId());
            if (vipUserDO.getTotalTime().intValue() > 0 && vipUserDO.getTotalTime().intValue() == vipUserDO.getUseredTime().intValue()) {
                vipUserDO.setTotalTime(inviteCodeDO.getGiveMinutes().longValue());
                vipUserDO.setUseredTime(0L);
                vipUserDO.setStatus(VipUserDO.StatusEnum.valid.code);
            } else {
                vipUserDO.setTotalTime(vipUserDO.getTotalTime() + inviteCodeDO.getGiveMinutes().longValue());
            }
        } else {
            //邀请人无记录
            vipUserDO = VipUserDO.builder().userId(inviteCodeDO.getUserId()).level(VipUserDO.LevelEnum.FREE_TIMES.code).status(VipUserDO.StatusEnum.valid.code).totalTime(inviteCodeDO.getGiveMinutes().longValue()).useredTime(0L).build();
        }
        if (Objects.nonNull(inviteVipUserDO)) {
            //被邀请人有记录
            inviteVipUserDO = lockById(inviteVipUserDO.getId());
            if (inviteVipUserDO.getTotalTime().intValue() > 0 && inviteVipUserDO.getTotalTime().intValue() == inviteVipUserDO.getUseredTime().intValue()) {
                inviteVipUserDO.setTotalTime(inviteCodeDO.getGiveMinutes().longValue());
                inviteVipUserDO.setUseredTime(0L);
                inviteVipUserDO.setStatus(VipUserDO.StatusEnum.valid.code);
            } else {
                inviteVipUserDO.setTotalTime(inviteVipUserDO.getTotalTime() + inviteCodeDO.getGiveMinutes().longValue());
            }
        } else {
            //被邀请人无记录
            inviteVipUserDO = VipUserDO.builder().userId(inviteCodeDO.getInviteUserId()).level(VipUserDO.LevelEnum.FREE_TIMES.code).status(VipUserDO.StatusEnum.valid.code).totalTime(inviteCodeDO.getGiveMinutes().longValue()).useredTime(0L).build();
        }
        vipUserRepository.save(vipUserDO);
        vipUserRepository.save(inviteVipUserDO);
    }

    private void setChargingLeaveValue(Long userId, Long lianMaiId, Integer leave) {
        redisTemplate.opsForValue().set(getChargingLeaveKey(userId, lianMaiId), leave, 10, TimeUnit.HOURS);
    }

    private Integer getChargingLeaveValue(Long userId, Long lianMaiId) {
        return (Integer) redisTemplate.opsForValue().get(getChargingLeaveKey(userId, lianMaiId));
    }

    private String getChargingLeaveKey(Long userId, Long lianMaiId) {
        return VIP_USER_CHARGING_LEAVE + userId + "-lainmai" + lianMaiId;
    }

    /**
     * 清洗数据
     */
    public void cleanData() {

        List<BigInteger> userIds = vipUserUseRecordRepository.findDistinctUserIdBySql();
        List<Integer> types = new ArrayList<>();
        types.add(VipSetMealDO.TypeEnum.month_card.code);
        types.add(VipSetMealDO.TypeEnum.season_card.code);
        types.add(VipSetMealDO.TypeEnum.year_card.code);
        List<VipSetMealDO> vipSetMealDOS = vipSetMealRepository.findAllByTypeIn(types);
        List<Long> setMealIds = vipSetMealDOS.stream().map(vipSetMealDO -> vipSetMealDO.getId()).collect(Collectors.toList());

        Map<Long, Long> setMealIdToMonths = new HashMap<>();
        vipSetMealDOS.forEach(vipSetMealDO -> {
            setMealIdToMonths.put(vipSetMealDO.getId(), vipSetMealDO.getMonths());
        });
        userIds.forEach(aLong -> {
            handleAlone(setMealIds, setMealIdToMonths, aLong);

        });

    }

    private void handleAlone(List<Long> setMealIds, Map<Long, Long> setMealIdToMonths, BigInteger aLong) {
        //用户使用记录信息
        List<VipUserUseRecordDO> recordDOList = vipUserUseRecordRepository.findAllByUserIdAndLevelOrderByCreateTime(aLong.longValue(), VipUserDO.LevelEnum.VIP_COMMON.code);
        //用户购卡记录信息
        List<VipUserSetMealRecordDO> setMealRecordDOS = vipUserSetMealRecordRepository.findAllByUserIdAndOperateTypeAndSetMealIdInOrderByCreateTimeAsc(aLong.longValue(), VipUserSetMealRecordDO.OperateEnums.BUY.code, setMealIds);

        int fromIndex = 0;
        int toIndex = 0;
        boolean flag = false;
        for (int i = 0; i < setMealRecordDOS.size(); i++) {
            VipUserSetMealRecordDO vipUserSetMealRecordDO = setMealRecordDOS.get(i);
            Long setMealId = vipUserSetMealRecordDO.getSetMealId();
            Long months = setMealIdToMonths.get(setMealId);
            toIndex += months;
            if (recordDOList.size() < toIndex) {
                toIndex = recordDOList.size();
                flag = true;
            }
            List<VipUserUseRecordDO> subList = recordDOList.subList(fromIndex, toIndex);

            List<VipUserUseRecordDO> updateDatas = subList.stream().filter(vipUserUseRecordDO -> Objects.isNull(vipUserUseRecordDO.getSetMealRecordId())).map(vipUserUseRecordDO -> {
                vipUserUseRecordDO.setSetMealId(setMealId);
                vipUserUseRecordDO.setSetMealRecordId(vipUserSetMealRecordDO.getId());
                return vipUserUseRecordDO;
            }).collect(Collectors.toList());

            vipUserUseRecordRepository.save(updateDatas);
            if (flag) {
                break;
            }
            fromIndex += months.intValue();
        }
    }

    public List<Long> findAllExpireUser() {
        return vipUserRepository.findAllExpireUser();
    }

    private List<String> allOpenDays() {
        DicDataDO data = dicService.findByCode(STUDY_OPEN_DAYS.dicName()).filter(STUDY_OPEN_DAYS.days.name());
        return Arrays.asList(data.getData().split(","));
    }

    public boolean isOpenDay() {
        return allOpenDays().contains(DateUtil.getToday(formatter));
    }
}
